package com.briup.pts.web.interceptor;

import com.briup.pts.config.auth.SecurityUserContext;
import com.briup.pts.config.auth.TokenMapContext;
import com.briup.pts.config.exception.ServiceException;
import com.briup.pts.config.response.ResultCode;
import com.briup.pts.domain.bean.Account;
import com.briup.pts.service.UserService;
import com.briup.pts.utils.JwtUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

// 这个类需要被Spring框架创建对象，并且需要被注册到SpringMVC组件中
@Component
@RequiredArgsConstructor
public class AuthInterceptor implements HandlerInterceptor {

    private final UserService userService;
    @Value("${auth.header-name}")
    private String headerName;

    // preHandle()方法 能够在请求进入到 Controller之前拦截下来，然后对请求进行初步的处理
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 在浏览器请求跨域前会发送一个 Options请求，用于查询服务器是否支持跨域；
        // Options请求不会携带任何参数，所以校验token，直接放行即可
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())){
            return true;
        }

        // 如果不是OPTIONS请求，那么需要校验token
        String token = request.getHeader(headerName);
        String userId = JwtUtil.fieldValue(token, "accountId", String.class);

        // 根据用户id从TokenMapContext中获取token，
        // 如果不存在token，那么说明没有登录或当前token是被泄露的
        String token2 = TokenMapContext.get(userId);
        // 如果根据用户id在TokenMapContext中没有获取到token，或token为空，那么直接返回未登录
        if (!StringUtils.hasText(token2)){
            throw new ServiceException(ResultCode.TOKEN_IS_MISSING);
        }else if (!token.equals(token2)){
            // 必须保证传入的token与其对应用户存储在TokenMapContext中的token保持一致
            throw new ServiceException(ResultCode.TOKEN_IS_INVALID);
        }

        // 根据用户id，获取当前登录用户的数据
        Account user = userService.queryById(userId);
        if (user == null){
            throw new ServiceException(ResultCode.ACCOUNT_IS_UNABLE);
        }
        // 如果用户没有被禁用，那么继续将数据放入到 用户认证上下文中
        SecurityUserContext.setCurrentUser(user);

        return true;
    }

    // afterCompletion() 方法 指定是在请求所有任务都执行完毕后，会执行本方法
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 清除本请求在 上下文中 存储的用户数据
        SecurityUserContext.clear();
    }
}
